//--------------------------------------------------------------------------------------------------
//                                                   ___  __  __  __  __  __  __  __    __  __  ___ 
// Copyright 2010, Koert van der Veer.              / __)(  )(  )(  \/  )(  )(  )(  )  (  )(  )/ __)
// Licensed under the Apache License,              ( (__  )(__)(  )    (  )(__)(  )(__  )(__)( \__ \
// version 2.0; ("the License")                     \___)(______)(_/\/\_)(______)(____)(______)(___/

// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//     http://www.apache.org/licenses/LICENSE-2.0 
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License. 
//--------------------------------------------------------------------------------------------------
// This file is part of cumulus,  an memory manager implementation in C++.  Details are discussed in
// my blog,  which can be found at http://ondergetekende.nl.  Comments,  suggestions and bug reports
// are welcome. They can be added as comments at the blog. Be advised that in its current form, this
// code is most likely not production ready; it simply hasn't been tested sufficiently. 
//--------------------------------------------------------------------------------------------------
// Layer which prepends a header. This header can be accessed using the GetHeader function in misc.h
// Note that no construction or destruction takes place on the headers.
//--------------------------------------------------------------------------------------------------

#pragma once

namespace Cumulus
{
template< typename HeaderType, typename InnerLayer >
class PrependHeader : public InnerLayer
{
public:

	// Alignment is the alignment of the header, at best.
	static const size_t MinAlignment = 
		( AlignmentOf<HeaderType>::Result < InnerLayer::MinAlignment) ? 
		AlignmentOf<HeaderType>::Result :
		InnerLayer::MinAlignment;

	// Minimum size is reduced by the header, but still at least 1.
	static const size_t MinSize = (sizeof(HeaderType) >= InnerLayer::MinSize ) ?
		1 :
		InnerLayer::MinSize - sizeof(HeaderType);

	/// Returns a pointer to the a new piece of memory, of at least Size.
	/// After the call, Size may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param  Size	in/out The requested size of the new memory allocation. Must be > 0. 
	///					Post: the size usable for writes, larger than
	/// @return			A pointer to the new memory block, or NULL if the allocation was
	/// 				not successful.
	void* Malloc( size_t& Size );

	/// Returns ownership of a block of memory to the memory manager.        
	/// @param  Memory	Pointer to the allocation be released. Must be a valid pointer, as 
	//					previously returned by Malloc. (NULL is invalid)
	void Free( void* Memory );

	/// Returns the client usable size for the provided pointer
	/// This is guaranteed to be equal or larger than the request size provided to Malloc.
	/// Successive calls to GetSize will return the same value, unless Resize was called earlier.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	size_t GetSize( void* Memory );

	/// Tries to change the size of the allocated block to at least NewSize.
	/// After the call, NewSize may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param Memory	Pointer to allocation to be modified. Must be a valid pointer, as previously
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	/// @param NewSize	in/out The new size for the allocation
	/// @return			TRUE if the resize operation was successful.
	bool Resize( void* Memory, size_t& NewSize );

	/// Enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the first Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetNext( void* Memory );

	/// Reverse enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the last Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetPrevious( void* Memory );
};

template< typename HeaderType, typename InnerLayer >
void* PrependHeader<HeaderType,InnerLayer>::Malloc( size_t& Size ) 
{
	// pad the size to make room for the header
	Size += sizeof( HeaderType );

	HeaderType* Result = (HeaderType*) InnerLayer::Malloc( Size );

	// Don't report size of the header to the client
	Size -= sizeof( HeaderType );

	// Return payload
	return GetPayload( Result );
}

template< typename HeaderType, typename InnerLayer >
void PrependHeader<HeaderType,InnerLayer>::Free( void* Memory ) 
{
	// Get the pointer including the header
	HeaderType* Header = GetHeader<HeaderType>( Memory );

	InnerLayer::Free( Header );
}

// Returns the client usable size for the provided pointer
template< typename HeaderType, typename InnerLayer >
size_t PrependHeader<HeaderType,InnerLayer>::GetSize( void* Memory )
{
	// Get the pointer including the header
	HeaderType* Header = GetHeader<HeaderType>( Memory );

	size_t Result = InnerLayer::GetSize( Header );

	// remove header from size
	Result -= sizeof( HeaderType );

	return Result;
}

// Tries to change the size of the allocated block to at least NewSize.
// If the request can be satisfied, the new usable size is returned,
// otherwise 0 is returned.
template< typename HeaderType, typename InnerLayer >
bool PrependHeader<HeaderType,InnerLayer>::Resize( void* Memory, size_t& NewSize )
{
	// Adjust size to accommodate the header
	NewSize += sizeof(HeaderType);

	HeaderType* Header = GetHeader<HeaderType>( Memory );
	bool Result = InnerLayer::Resize( Header, NewSize );

	// Don't report size of the header to the client
	NewSize -= sizeof(HeaderType);

	return Result;
}

template< typename HeaderType, typename InnerLayer >
void* PrependHeader<HeaderType,InnerLayer>::GetNext( void* Memory ) 
{ 
	HeaderType* Header = GetHeader<HeaderType>( Memory );
	HeaderType* NextHeader = (HeaderType*) InnerLayer::GetNext( Header );

	if( NextHeader == NULL )
	{
		return NULL;
	}
	else
	{
		return GetPayload( NextHeader );
	}
}

template< typename HeaderType, typename InnerLayer >
void* PrependHeader<HeaderType,InnerLayer>::GetPrevious( void* Memory ) 
{ 
	HeaderType* Header = GetHeader<HeaderType>( Memory );
	HeaderType* NextHeader = (HeaderType*) InnerLayer::GetPrevious( Header );

	if( NextHeader == NULL )
	{
		return NULL;
	}
	else
	{
		return GetPayload( NextHeader );
	}
}

}